#include <ir/genarator.h>

#define CHECK_TYPE(type_out, reg1, reg2) \
  Assert(reg1->get_type() == reg2->get_type(), \
      "type not same: %s != %s", \
      enum_name(reg1->get_type()).c_str(), \
      enum_name(reg2->get_type()).c_str()); \
  type_out = reg1->get_type();

namespace ir {
  Genarator::Genarator(CompUnit * comp_unit) {
    this->comp_unit = comp_unit;
    this->block_i = 0;
    this->reg_i = 0;
  }

  Func * Genarator::create_func(const std::string & name, const VarType func_type) {
    Func * func = new Func();
    func->func_type = func_type;
    func->name = name;
    this->comp_unit->func_list.push_back(func);
    return func;
  }

  Reg * Genarator::gen_inst_imm(Block & block, VarType reg_type, int int_number) {
    InstImm * inst_imm = new InstImm();
    Reg * reg = create_reg(block, reg_type);
    inst_imm->reg = reg;
    inst_imm->type = VarType::i32;
    inst_imm->int_number = int_number;
    block.inst_list.push_back(inst_imm);
    return reg;
  }

  Reg * Genarator::gen_inst_imm(Block & block, VarType reg_type, float float_number) {
    InstImm * inst_imm = new InstImm();
    Reg * reg = create_reg(block, reg_type);
    inst_imm->reg = reg;
    inst_imm->type = VarType::f32;
    inst_imm->float_number = float_number;
    block.inst_list.push_back(inst_imm);
    return reg;
  }

  Reg * Genarator::gen_inst_arg_alloc(Func & func, VarType type) {
    VarType arg_type = VarType::none;
    switch(type) {
    case VarType::i32:
      arg_type = VarType::ptr_i32;
      break;
    case VarType::f32:
      arg_type = VarType::ptr_f32;
      break;
    default:
      Panic("arg alloc invalid type:%s", enum_name(type).c_str());
    }
    Reg * arg = create_arg(func, arg_type);
    return arg;
  }

  Reg * Genarator::gen_inst_alloc(Block & block, VarType type) {
    InstAlloc * inst_alloc = new InstAlloc();
    VarType reg_type = VarType::none;
    uint32_t size = 0;
    switch(type) {
    case VarType::i32:
      size = 4;
      reg_type = VarType::ptr_i32;
      break;
    case VarType::f32:
      size = 4;
      reg_type = VarType::ptr_f32;
      break;
    default:
      Panic("can't alloc type:%s", enum_name(type).c_str());
    }
    Reg * reg = create_reg(block, reg_type);
    inst_alloc->reg = reg;
    inst_alloc->size = size;
    block.inst_list.push_back(inst_alloc);
    return reg;
  }

  void Genarator::gen_inst_store(Block & block, Reg * rd, Reg * src) {
    InstStore * inst_store = new InstStore();
    inst_store->rd = rd;
    inst_store->src = src;
    src->add_used_count();
    block.inst_list.push_back(inst_store);
  }

  Reg * Genarator::gen_inst_load(Block & block, Reg * src) {
    InstLoad * inst_load = new InstLoad();
    VarType reg_type = VarType::none;
    switch(src->get_type()) {
    case VarType::ptr_i32:
      reg_type = VarType::i32;
      break;
    case VarType::ptr_f32:
      reg_type = VarType::f32;
      break;
    default:
      Panic("load invalide type:%s",
          enum_name(src->get_type()).c_str());
    }
    Reg * reg = create_reg(block, reg_type);
    inst_load->rd = reg;
    inst_load->src = src;
    // src->add_used_count();
    block.inst_list.push_back(inst_load);
    return reg;
  }

  Reg * Genarator::gen_inst_array_alloc(Block & block, VarType type, uint32_t count) {
    InstArrayAlloc * inst_array_alloc = new InstArrayAlloc();
    VarType reg_type = VarType::none;
    uint32_t size = 0;
    switch(type) {
    case VarType::i32:
      reg_type = VarType::ptr_i32;
      size = 4;
      break;
    case VarType::f32:
      reg_type = VarType::ptr_f32;
      size = 4;
      break;
    default:
      Panic("alloc invalid type:%s", enum_name(type).c_str());
    }
    Reg * reg = create_reg(block, reg_type);
    inst_array_alloc->reg = reg;
    inst_array_alloc->size = size;
    inst_array_alloc->count = count;
    block.inst_list.push_back(inst_array_alloc);
    return reg;
  }

  void Genarator::gen_inst_array_store(Block & block, Reg * rd, Reg * src, Reg * index) {
    InstArrayStore * inst_array_store = new InstArrayStore();
    inst_array_store->rd = rd;
    // rd->add_used_count();
    inst_array_store->src = src;
    src->add_used_count();
    inst_array_store->index = index;
    index->add_used_count();
    block.inst_list.push_back(inst_array_store);
  }

  Reg * Genarator::gen_inst_array_load(Block & block, Reg * src, Reg * index) {
    InstArrayLoad * inst_array_load = new InstArrayLoad();
    VarType reg_type = VarType::none;
    switch(src->get_type()) {
    case VarType::ptr_i32:
      reg_type = VarType::i32;
      break;
    case VarType::ptr_f32:
      reg_type = VarType::f32;
      break;
    default:
      Panic("array load invalid type:%s",
          enum_name(src->get_type()).c_str());
    }
    Reg * rd = create_reg(block, reg_type);
    inst_array_load->rd = rd;
    inst_array_load->src = src;
    // src->add_used_count();
    inst_array_load->index = index;
    index->add_used_count();
    block.inst_list.push_back(inst_array_load);
    return rd;
  }

  GlobalReg * Genarator::gen_inst_global_alloc(VarType type, std::string iden, int int_number) {
    InstGlobalAlloc * inst_global_alloc = new InstGlobalAlloc();
    VarType reg_type = VarType::none;
    switch(type) {
    case VarType::i32:
      reg_type = VarType::ptr_i32;
      break;
    case VarType::f32:
      reg_type = VarType::ptr_f32;
      break;
    default:
      Panic("global alloc invalid type:%s", enum_name(type).c_str());
    }
    GlobalReg * reg = create_global_reg(iden, reg_type);
    inst_global_alloc->type = VarType::i32;
    inst_global_alloc->global_reg = reg;
    inst_global_alloc->int_number = int_number;
    this->comp_unit->inst_global_alloc_list.push_back(inst_global_alloc);
    return reg;
  }

  void Genarator::gen_inst_global_store(Block & block, GlobalReg * global_rd, Reg * src) {
    InstGlobalStore * inst_global_store = new InstGlobalStore();
    inst_global_store->global_rd = global_rd;
    inst_global_store->src = src;
    src->add_used_count();
    block.inst_list.push_back(inst_global_store);
  }

  Reg * Genarator::gen_inst_global_load(Block & block, GlobalReg * global_src) {
    InstGlobalLoad * inst_global_load = new InstGlobalLoad();
    VarType reg_type = VarType::none;
    switch(global_src->get_type()) {
    case VarType::ptr_i32:
      reg_type = VarType::i32;
      break;
    case VarType::ptr_f32:
      reg_type = VarType::f32;
      break;
    default:
      Panic("global load invalid type:%s",
          enum_name(global_src->get_type()).c_str());
    }
    Reg * reg = create_reg(block, reg_type);
    inst_global_load->rd = reg;
    inst_global_load->global_src = global_src;
    block.inst_list.push_back(inst_global_load);
    return reg;
  }

  Reg * Genarator::gen_inst_i32_to_f32(Block & block, Reg * src) {
    InstI32ToF32 * inst_i32_to_f32 = new InstI32ToF32();
    Assert(src->get_type() == VarType::i32, "invalid type:%s",
        enum_name(src->get_type()).c_str());
    Reg * rd = create_reg(block, VarType::f32);
    inst_i32_to_f32->rd = rd;
    inst_i32_to_f32->src = src;
    src->add_used_count();
    block.inst_list.push_back(inst_i32_to_f32);
    return rd;
  }

  Reg * Genarator::gen_inst_f32_to_i32(Block & block, Reg * src) {
    InstF32ToI32 * inst_f32_to_i32 = new InstF32ToI32();
    Assert(src->get_type() == VarType::f32, "invalid type:%s",
        enum_name(src->get_type()).c_str());
    Reg * rd = create_reg(block, VarType::i32);
    inst_f32_to_i32->rd = rd;
    inst_f32_to_i32->src = src;
    src->add_used_count();
    block.inst_list.push_back(inst_f32_to_i32);
    return rd;
  }

  Reg * Genarator::gen_inst_call(Block & block, Func * func, std::vector<Reg*> args) {
    InstCall * inst_call = new InstCall();
    Reg * reg = create_reg(block, func->func_type);
    inst_call->rd = reg;
    inst_call->func = func;
    inst_call->args = args;
    for(Reg * reg : args) {
      reg->add_used_count();
    }
    block.inst_list.push_back(inst_call);
    return reg;
  }

  void Genarator::gen_inst_ret(Block & block, VarType type, Reg * reg) {
    InstRet * inst_ret = new InstRet();
    Assert(type == VarType::none || type == reg->get_type(),
        "ret type:%s is not reg type:%s",
        enum_name(type).c_str(), enum_name(reg->get_type()).c_str());
    inst_ret->type = type;
    inst_ret->reg = reg;
    if(type != VarType::none)
      reg->add_used_count();
    block.inst_list.push_back(inst_ret);
  }

  Reg * Genarator::gen_inst_add(Block & block, Reg * src1, Reg * src2) {
    InstAdd * inst_add = new InstAdd();
    VarType reg_type = VarType::none;
    CHECK_TYPE(reg_type, src1, src2);
    Reg * reg = create_reg(block, reg_type);
    inst_add->rd = reg;
    inst_add->src1 = src1;
    src1->add_used_count();
    inst_add->src2 = src2;
    src2->add_used_count();
    block.inst_list.push_back(inst_add);
    return reg;
  }

  Reg * Genarator::gen_inst_sub(Block & block, Reg * src1, Reg * src2) {
    InstSub * inst_sub = new InstSub();
    VarType reg_type = VarType::none;
    CHECK_TYPE(reg_type, src1, src2);
    Reg * reg = create_reg(block, reg_type);
    inst_sub->rd = reg;
    inst_sub->src1 = src1;
    src1->add_used_count();
    inst_sub->src2 = src2;
    src2->add_used_count();
    block.inst_list.push_back(inst_sub);
    return reg;
  }

  Reg * Genarator::gen_inst_mul(Block & block, Reg * src1, Reg * src2) {
    InstMul * inst_mul = new InstMul();
    VarType reg_type = VarType::none;
    CHECK_TYPE(reg_type, src1, src2);
    Reg * reg = create_reg(block, reg_type);
    inst_mul->rd = reg;
    inst_mul->src1 = src1;
    src1->add_used_count();
    inst_mul->src2 = src2;
    src2->add_used_count();
    block.inst_list.push_back(inst_mul);
    return reg;
  }

  Reg * Genarator::gen_inst_div(Block & block, Reg * src1, Reg * src2) {
    InstDiv * inst_div = new InstDiv();
    VarType reg_type = VarType::none;
    CHECK_TYPE(reg_type, src1, src2);
    Reg * reg = create_reg(block, reg_type);
    inst_div->rd = reg;
    inst_div->src1 = src1;
    src1->add_used_count();
    inst_div->src2 = src2;
    src2->add_used_count();
    block.inst_list.push_back(inst_div);
    return reg;
  }

  void Genarator::gen_inst_jmp(Block & block, Block * block_dst) {
    InstJmp * inst_jmp = new InstJmp();
    inst_jmp->block = block_dst;
    block.inst_list.push_back(inst_jmp);
  }

  void Genarator::gen_inst_bne(Block & block, Reg * src1, Reg * src2, Block * block_dst) {
    InstBne * inst_bne = new InstBne();
    inst_bne->src1 = src1;
    src1->add_used_count();
    inst_bne->src2 = src2;
    src2->add_used_count();
    inst_bne->block = block_dst;
    block.inst_list.push_back(inst_bne);
  }

  void Genarator::gen_inst_beq(Block & block, Reg * src1, Reg * src2, Block * block_dst) {
    InstBeq * inst_beq = new InstBeq();
    inst_beq->src1 = src1;
    src1->add_used_count();
    inst_beq->src2 = src2;
    src2->add_used_count();
    inst_beq->block = block_dst;
    block.inst_list.push_back(inst_beq);
  }

  Block * Genarator::create_block(Func & func) {
    Block * block = new Block(++this->block_i);
    func.block_list.push_back(block);
    return block;
  }

  Block * Genarator::create_begin_block(Func & func) {
    Block * block = new Block(++this->block_i);
    func.block_list.push_back(block);
    return block;
  }

  Reg * Genarator::create_arg(Func & func, VarType var_type) {
    Reg * reg = new Reg(++this->reg_i, var_type);
    func.arg_list.push_back(reg);
    return reg;
  }

  Reg * Genarator::create_reg(Block & block, VarType var_type) {
    Reg * reg = new Reg(++this->reg_i, var_type);
    block.reg_list.push_back(reg);
    return reg;
  }

  ArrayInfo * Genarator::create_array(Func & func, ArrayType & arr_size) {
    ArrayInfo * array_info = new ArrayInfo(arr_size);
    func.array_info_list.push_back(array_info);
    return array_info;
  }

  GlobalReg * Genarator::create_global_reg(std::string iden, VarType type) {
    GlobalReg * global_reg = new GlobalReg(iden, type);
    this->comp_unit->global_reg_list.push_back(global_reg);
    return global_reg;
  }
}
